/*
    Seminar Computergrafik SS2012
    Praktikum
    Fachhochschule Münster
    Fachbereich Elektrotechnik und Informatik    
    
    Autoren: Alexander Kuhn & Mark Ruffer
*/

/**
 * Push Funktion für die model view Matrix
 */ 
function mvPushMatrix(){
    var copy = mat4.create();
    mat4.set(mvMatrix, copy);
    mvMatrixStack.push(copy);
}

/**
 * Pop Funktion für die model view Matrix
 */ 
function mvPopMatrix(){
    if(mvMatrixStack.length == 0){
        throw("Pop Aufruf an leerem Stack. Aufrufe prüfen!");
    }
    else {
        mvMatrix = mvMatrixStack.pop();
    }
}

/**
 * Berechnet die Radiant für gegebene Winkel in Grad
 * @param deg Winkel in Grad
 * @return Winkel in Radiant
 */   
function degToRad(deg){
    return deg * Math.PI / 180.0;
}

/**
 * Baue einen Array mit jeder Farbe 4x nacheinander
 * @param Array mit den einzelnen Farben
 * @return Array mit allen Farben
 */   
function unpackColors(packedColors){
    var i, j, unpackedColors, color;
    unpackedColors = [];
    for (i in packedColors) {
      color = packedColors[i];
      for (j=0; j < 2; j++) {
        unpackedColors = unpackedColors.concat(color);
        unpackedColors = unpackedColors.concat([1.0, 1.0, 1.0, 1.0]);
        unpackedColors = unpackedColors.concat([1.0, 1.0, 1.0, 1.0]);
      }
    }
    return unpackedColors;
}

/**
 * Berechnet den Vertex Array für einen Würfel
 * @param x X Position untere linke Ecke
 * @param y Y Positiion untere linke Ecke
 * @param z Z Position untere linke Ecke
 * @param width Breite des Würfels
 * @param height Höhe des Würfels
 * @param depth Tiefe des Würfels
 * @return Vertex Array (front, hinten, oben, unten, rechts, links)
 */        
function cubeArray(x, y, z, width, height, depth){
    var array = [];
    array = [
        x, y, z, x+width, y, z, x+width, y+height, z, x, y+height, z,
        x, y, z-depth, x+width, y, z-depth, x+width, y+height, z-depth, x, y+height, z-depth,
        x, y+height, z, x+width, y+height, z, x+width, y+height, z-depth, x, y+height, z-depth,
        x, y, z, x+width, y, z, x+width, y, z-depth, x, y, z-depth,
        x+width, y, z, x+width, y, z-depth, x+width, y+height, z-depth, x+width, y+height, z,
        x, y, z, x, y, z-depth, x, y+height, z-depth, x, y+height, z
    ];
    return array;
}

/**
 * Bringe die JavaScript Matrizen auf den Grafikkartenspeicher
 */
function setMatrixUniforms() {
    var normalMatrix;
    gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
    gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
    /* Normalenmatrix erzeugen mit Inverser mvMatrix und transponieren */
    normalMatrix = mat3.create();
    mat4.toInverseMat3(mvMatrix, normalMatrix);
    mat3.transpose(normalMatrix);
    gl.uniformMatrix3fv(shaderProgram.nMatrixUniform, false, normalMatrix);
}

/**
 * Lädt die angegebenen Texturen in den entsprechenden Speicher auf der Grafikkarte.
 * Arraylängen müssen identisch sein!
 * @param glTextureNumbers Array mit den Speicheradressen für die Texture (gl.TEXTURE0-31)
 * @param glTextureTypes Array mit den Texturtypen (gl.TEXTURE_2D ...)
 * @param textures Array mit den Texturen
 * @return true bei Erfolg, false bei Fehlern
 */      
function bindTextures(glTextureNumbers, glTextureTypes, textures){
    var i;
    
    if(glTextureNumbers.length != glTextureTypes.length || glTextureNumbers.length != textures.length){
        return false;
    }
    
    for(i=0; i<glTextureNumbers.length; i++){
        gl.activeTexture(glTextureNumbers[i]);
        gl.bindTexture(glTextureTypes[i], textures[i]);
    }
    return true;
}

/**
 * Lädt die entsprechende Texture und setzt bestimmte Parametre
 * @param texture Die Texture
 * @param magFilter e.g. gl.LINEAR gl.NEAREST gl.LINEAR_MIPMAP_NEAREST
 * @param minFilter ...  
 */  
function handleLoadedTexture(texture, magFilter, minFilter){
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, magFilter);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter);
    if(magFilter == gl.LINEAR_MIPMAP_NEAREST || minFilter == gl.LINEAR_MIPMAP_NEAREST){
        gl.generateMipmap(gl.TEXTURE_2D);
    }
    gl.bindTexture(gl.TEXTURE_2D, null);
}

/**
 * Lädt die entsprechenden Speichereinheiten und führt drawElements aus
 * @param gl das WebGL Objekt
 * @param shadProg das ShaderProgram
 * @param drawElementType 
 * @param vBuffer
 * @param vBufferArrayType
 * @param vBufferNumberType
 * @param iBuffer
 * @param iBufferArrayType
 * @param iBufferNumberType
 * @param tBuffer
 * @param tBufferArrayType
 * @param tBufferNumberType
 * @param nBuffer
 * @param nBufferArrayType
 * @param nBufferNumberType   
 * @param textureNumber 
 */         
function bindAndDraw(gl, shadProg, drawElementType, 
                     vBuffer, vBufferArrayType, vBufferNumberType, 
                     iBuffer, iBufferArrayType, iBufferNumberType, 
                     tBuffer, tBufferArrayType, tBufferNumberType,
                     nBuffer, nBufferArrayType, nBufferNumberType,
                     textureNumber
                     ){
    /* Vertexe */
    gl.bindBuffer(vBufferArrayType, vBuffer);
    gl.vertexAttribPointer(shadProg.vertexPositionAttribute, vBuffer.itemSize, vBufferNumberType, false, 0, 0);
    /* Licht */
    gl.bindBuffer(nBufferArrayType, nBuffer);
    gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, nBuffer.itemSize, nBufferNumberType, false, 0, 0);
    /* Textur */
    gl.bindBuffer(tBufferArrayType, tBuffer);
    gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, tBuffer.itemSize, tBufferNumberType, false, 0, 0);
    /* tell which preloaded texture to use */
    gl.uniform1i(shaderProgram.samplerUniform, textureNumber);
    /* Indizes */
    gl.bindBuffer(iBufferArrayType, iBuffer);
    setMatrixUniforms();
    gl.drawElements(drawElementType, iBuffer.numItems, iBufferNumberType, 0);
}

/**
 * Initializes the given buffer.
 * - create WebGL Buffer
 * - fill in the data
 * - set itemSize (count of numbers for one item)
 * - set numItems (number of items)
 * 
 * @param gl the WebGL context
 * @param bufferType the type of the buffer (gl.ARRAY_BUFFER etc)
 * @param values an Array containing the values of the buffers items
 * @param drawType the WebGL drawtype (gl.STATIC_DRAW etc)
 * @param itemSize
 * @param numItems
 * @param arrayType (0:Float32Array, 1:Uint16Array) 
 * @return a filled and extended buffer or null in error    
 */            
function initBuffer(gl, bufferType, values, drawType, items, itemSize, arrayType){
    var buffer, array;

    switch(arrayType){
        case 0: array = new Float32Array(values); break;
        case 1: array = new Uint16Array(values); break;
        default: 
            window.alert("Wrong Array Type! Returning null."); 
            return null;
    }    
    buffer = gl.createBuffer();
    gl.bindBuffer(bufferType, buffer);
    gl.bufferData(bufferType, array, drawType);
    buffer.itemSize = itemSize;
    buffer.numItems = items;
    return buffer;
}

/**
 * Erzeugt einen Shader
 * 
 * @param gl the WebGL Context  
 * @param shader_type Typ des Shaders (gl.VERTEX_SHADER, gl.FRAGMENT_SHADER)
 * @param shader_source String Variable mit dem Shader Quelltext
 * @return Erzeugter Shader bzw. null bei Fehlern   
 */   
function getShader(gl, shader_type, shader_source) {
    var shader;

    /* Shader erzeugen */
    shader = gl.createShader(shader_type);

    /* Shaderquelltext laden und kompilieren */
    gl.shaderSource(shader, shader_source);
    gl.compileShader(shader);

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        window.alert("Compiling Error: "+gl.getShaderInfoLog(shader));
        return null;
    }

    return shader;
}

/**
 * Initialisiere das WebGL Objekt aus dem Canvas
 * @param canvas Die HTML Zeichenfläche
 */  
function initGL(canvas){
    try {
        gl = canvas.getContext("experimental-webgl");
        gl.viewportWidth = canvas.width;
        gl.viewportHeight = canvas.height;                
    }
    catch(e){
        window.alert("Fehler: " + e.message);
    }
                                   
    if(!gl){
        window.alert("WebGL konnte nicht initialisiert werden.");
    }
}